How to create NICE style graphics

The niceRplots package and cookbook make the process of creating publication-ready graphics in the NICE style a more reproducible process, as well as making it easier for people new to R to create graphics.

Within this cookbook, we will demonstrate how to use the functions included within the niceRplots package. We will also provide examples of plots that have been created in the NICE style.

Load the relevant packages

Before we can start making charts, we first need to load all of the relevant packages. We wont load every package here, just the ones that we will use the most often. It is good practice to limit the number of loaded packages to prevent masking conflicts. For this reason, we will often use double colons :: to access specific functions within a package (e.g. readr::read_csv()), rather than loading the full package using library(pkg).

# Load in the relevant packages
library(dplyr)
library(ggplot2)
library(leaflet)

devtools::load_all()

# Set up the NICE colour palette
nice_colours <- c("#228096",
                  "#D07B4D",
                  "#37906D",
                  "#00436C",
                  "#EAD054")


How to style your charts

As an example we will create a simple bar chart using the iris dataset. We will first create a basic chart, and then add some additional formatting. We can see the two charts below with and without formatting.

# Wrangle data for plotting
iris_bar_df <- iris %>% 
  group_by(Species) %>% 
  summarise(Sepal_Width = mean(Sepal.Width)) %>% 
  ungroup()

# Create basic bar chart (left)
iris_bar <- ggplot(iris_bar_df) + 
  geom_col(aes(x = Species, y = Sepal_Width), ) +
  labs(title = "Basic chart title",
       subtitle = "Basic chart subtitle",
       x = "Species",
       y = "Sepal Width")

# Create formatted chart (right)
iris_bar_formatted <- ggplot(iris_bar_df) + 
  # Adjust bar fill and outline colour
  geom_col(aes(x = Species, y = Sepal_Width), fill = nice_colours[1], colour = "#000000")+
  # Add baseline to x axis
  geom_hline(yintercept = 0, linewidth = 1, colour = "#333333") +
  # set y-axis limits and remove padding
  scale_y_continuous(expand = c(0, 0), limits = c(0, 4)) +
  # Add labels 
  labs(title = "Formatted chart title",
       subtitle = "Formatted chart subtitle ",
       y = "Sepal width")

iris_bar
iris_bar_formatted


We can now apply the NICE theme using the nice_gg_theme() function from the niceRplots package, specifying the base font for the plot to be of size 12. The nice_gg_theme() function alters several aspects of the chart to make it consistent with the NICE brand guidelines. These are as follows:

  • Fonts: changes the title font to Lora SemiBold, and all other text to Inter Regular.

  • Text scaling: applies a text hierarchy to the chart using relative text sizes. Changing the optional base_size argument will change the text size throughout the plot. Titles and subtitles will be automatically scaled relative to this base_size to maintain the text hierarchy. This is useful if you want to change the chart size, as all text can be simply adjusted by changing the base_size argument.

  • Colour scheme: changes line and text colours to be consistent with the NICE brand colour palette. This is particularly noticeable when creating faceted charts (shown later in cookbook). It should be noted that this will not add colour to the plotted data, this will still need to be added manually when creating the chart. For instance, a fill colour was added in the geom_col() argument in the above example.

  • Margins: adjusts the margins around the title, subtitle, and axis titles to give everything space to breathe.

  • Background: removes the panel background and borders.

  • Other: Applies major grid lines along the y-axis. This will be appropriate for most charts, but may need to be disabled in others by applying theme(panel.grid.major.y = ggplot2::element_blank())

The nice_gg_theme() function isn’t designed to modify every aspect of the chart, but to apply a general theme that will maintain consistency with the NICE style guide across all charts. Many aspects of the chart will still need to be modified manually. For example, in the below chart we may choose to apply an additional theme argument to remove the x-axis title.

iris_bar_themed <- iris_bar_formatted +
  # Apply NICE theme, set base text size to 12
  nice_gg_theme(base_size = 12) +
  # remove x axis title as this is not needed
  theme(axis.title.x = ggplot2::element_blank())

iris_bar_themed


How to finalise your charts

Once a plot has been created and styled using nice_gg_theme(), the next step is to apply the finalise_plot() function. This function will create a footer containing information on the data source, as well as adding the NICE logo to the bottom right. It will then left-align the title, subtitle and footer. The finalise_plot() function has 3 arguments:

  • plot_name: the name of the plot object, in the example below we have saved ours as iris_bar_themed.

  • source_name: the source of the underlying data. We have used data from the iris dataset.

  • logo: this will determine whether the NICE logo is included in the footer. It can have a value of “NICE” to include the logo, or “none” to leave it blank.

# Apply the finalise plot function
finalised_plot <- finalise_plot(iris_bar_themed,
                                source_name = "Iris dataset",
                                logo = "NICE")

finalised_plot


How to save your finalised plots

After applying the finalise_plot() function, the chart can be saved using the ggsave() function.

# Save plot using the ggsave() function 
ggsave("finalised_plot.png", finalised_plot)


Static charts using ggplot2

The sections below provide examples of how to make different types of static chart using the ggplot2 package. These will demonstrate how to format different charts and apply the NICE theme. For some examples we will continue to use R’s built in iris dataset. For others we will use data on the monthly Sub-ICB location level prescribing of 5 direct oral anticoagulants (DOACS) in England between February 2017 and January 2022. This was downloaded from OpenPrescribing.net.

# Load in DOACs data
doacs_df <- readr::read_csv(here::here("DOACs_data.csv"),
                              col_types = "Dcccccdddd")


Histogram

# Create chart
nice_hist <- iris %>%
  ggplot() +
  geom_histogram(aes(x = Sepal.Width, fill = Species), 
                 binwidth = 0.2, color = "black") +
  # Add a line on the x axis
  geom_hline(yintercept = 0, linewidth = 1, colour = "#333333") +
  # Remove extra spacing and set y axis limits
  scale_y_continuous(expand = c(0,0), limits = c(0,40)) +
  # Use NICE colours
  scale_fill_manual(values = nice_colours) +
  # Add NICE theme and set labels
  nice_gg_theme() +
  labs(title = "Species setosa has the longest sepal length",
       subtitle = "Distribution of sepal width by species",
       x = "Sepal Width",
       y = "Frequency")

nice_hist

#finalise_plot(example_plot, source_name = "Source: Iris dataset")


Bar chart

# Prepare data, get total items dispensed for each medicine in 2021 (in millions)
bar_df <- doacs_df %>% 
  filter(between(date, as.Date("2021-01-01"), as.Date("2021-12-31"))) %>% 
  group_by(chemical) %>% 
  summarise(items = sum(items)/1000000) %>% 
  ungroup()

# Create chart
bar_chart <- bar_df %>%
  # Plot bars in decreasing order of items
  ggplot(aes(y = items, x = reorder(chemical, -items))) +
  # Add baseline to x axis
  geom_hline(yintercept = 0, linewidth = 1, colour = "#333333") +
  # set y-axis limits and remove padding
  scale_y_continuous(expand = c(0,0), limits = c(0,8)) +
  # set x-axis labels to wrap to prevent overlapping
  scale_x_discrete(labels = function(x) {stringr::str_wrap(x, width = 10)}) +
  # Add bars, add fill and border colours
  geom_col(fill = nice_colours[1], colour = "#000000") +
  # Apply NICE theme
  nice_gg_theme(base_size = 12) +
  # Remove the x-axis title
  theme(axis.title.x = ggplot2::element_blank()) +
  # Add labels
  labs(title = "Apixaban was the most prescribed DOAC in 2021",
       subtitle = "Total DOAC medicines dispensed in primary care in England, 2020",
       y = "Dispensed items (millions)")

bar_chart


Line chart

# Prepare data
line_df <- doacs_df %>%
  filter(chemical == "Edoxaban") %>% 
  group_by(date, chemical) %>%
  summarise(items = sum(items)) %>%
  ungroup()

# Create chart
line_chart <- line_df %>%
  ggplot(aes(x = date, y = items)) +
  # Add baseline to x axis
  geom_hline(yintercept = 0, linewidth = 1, colour="#333333") +
  # Add the vertical dashed line to show lockdown date. Added this before plotting the 
  # data so that it sits on a layer behind the line and points
  geom_vline(xintercept = as.Date("2020-03-01"), linewidth = 0.6, linetype = "dashed") +
  # Add the line and points, make both teal
  geom_line(linewidth = 0.8, colour = nice_colours[1]) +
  geom_point(size = 2, colour = nice_colours[1]) +
  # Set y-axis limits and remove padding, add commas to axis labels
  scale_y_continuous(expand = c(0,0), limits = c(0,200000), label = scales::comma) +
  # Set x-axis labels to show as month year, and to occur at 6-monthly intervals
  scale_x_date(date_labels = "%b\n%Y", date_breaks = "6 months") +
  # Apply NICE theme
  nice_gg_theme() +
  # Remove x-axis title
  theme(axis.title.x = ggplot2::element_blank()) +
  # Add labels
  labs(title = "Edoxaban prescribing has increased since 2017",
       subtitle = "Edoxaban prescribing in England, 2017-2022",
       y = "Dispensed items") +
  # Add annotation for dashed line
  annotate("text", x = as.Date("2020-02-01"), y = 180000, 
           label = "National lockdown\non 23 March", hjust = "right")

line_chart


Scatter plot

# Prepare data
scatter_df <- doacs_df %>%
  filter(chemical == "Apixaban",
         date == "2021-07-01") %>% 
  mutate(total_list_size = total_list_size/1000)

# Create chart
scatter_chart <- scatter_df %>%
  ggplot(aes(x = total_list_size, y = items)) +
  # Add the points and make them teal
  geom_point(colour = nice_colours[1], size = 2) +
  # set x-axis limits, adjust padding and add commas to axis labels
  scale_x_continuous(expand = expansion(mult = c(0, 0.05)), 
                     limits = c(0,3000), label = scales::comma) +
  # set y-axis limits and remove padding, add commas to axis labels
  scale_y_continuous(expand = c(0,0), limits = c(0, 25000), label = scales::comma) +
  # Apply NICE theme
  nice_gg_theme() +
  # Add a panel border and vertical grid lines
  theme(panel.border = ggplot2::element_rect(color = "#000000",
                                             linewidth = 0.3,
                                             fill = NA),
        panel.grid.major.x = ggplot2::element_line(color = "#BFBFBF")) +
  # Add labels
  labs(title = "Apixaban prescribing",
       subtitle = "Items of apixaban dispensed in primary care compared to\nsub-ICB location list size, July 2021",
       x = "Primary care list size (x1000)",
       y = "Dispensed items")

scatter_chart


Faceted chart

# Prepare data
facet_df <- doacs_df %>%
  filter(chemical %in% c("Apixaban", "Edoxaban", "Warfarin sodium"),
         between(date, as.Date("2020-01-01"), as.Date("2021-12-31"))) %>% 
  group_by(date, chemical) %>%
  summarise(items = sum(items)/1000) %>%
  ungroup()

# Create chart
facet_chart <- facet_df %>%
  ggplot(aes(x = date, y = items, color = chemical)) +
  # Add lines
  geom_line(linewidth = 1) +
  # Add points
  geom_point(size = 2) +
  ## set y-axis limits and remove padding
  scale_y_continuous(expand = c(0,0), limits = c(0,800)) +
  # set x-axis labels to show as month year, and to occur at 6-monthly intervals
  scale_x_date(date_labels = "%b\n%Y", date_breaks = "6 months") +
  # Manually set the colour of the lines and points
  scale_color_manual(values = nice_colours) +
  # APply facet wrapping to put each chemical into an individual plot
  facet_wrap(~chemical) +
  # Apply the NICE theme
  nice_gg_theme() +
  # Add a panel border, remove x axis title and legend
  theme(panel.border = ggplot2::element_rect(color = "#000000",
                                               linewidth = 0.3,
                                               fill = NA),
        axis.title.x = ggplot2::element_blank(),
        legend.position = "none") +
  # Add labels
  labs(title = "Trends in anti-coagulant prescribing",
       subtitle = "Prescribing of 3 anti-coagulants in England, 2020-2021",
       x = "Sepal Length",
       y = "Dispensed items (thousands)")

facet_chart


Choropleth chart (heatmap)

# Prepare the data
map_df <- doacs_df %>% 
  filter(between(date, as.Date("2021-01-01"), as.Date("2021-12-31")),
         chemical == "Apixaban") %>% 
  group_by(chemical, name, ods_code, gss_code) %>% 
  summarise(items_per_1000 = sum(items)/(sum(total_list_size)/1000),
            items = sum(items)) %>% 
  ungroup()

# Load in the dataframe containing the shapes for Sub ICB location areas
sub_icb_shapes_df <- sf::read_sf(here::here("inst/extdata/sub_icb_shapes_2022_04.geojson")) %>% 
  janitor::clean_names() %>% 
  select(-objectid)

# Load in the dataframe containing the shape for England - we will use this to make the map outline
national_shape_df <- sf::read_sf(here::here("inst/extdata/national_shape_2022_04.geojson")) %>% 
  janitor::clean_names()

# Join our data to the relevant Sub ICB location shapes. Ensure the dataframe containing the 
# shapes is the first argument in the join, as we want to preserve the class of this table. 
# If not the geometry column containing the shapes will be dropped.
map_df <- left_join(sub_icb_shapes_df, map_df, by = c("sicbl22cd" = "gss_code"))

# Set up the colour palette 
pal <- colorRampPalette((c("#BFBFBF", nice_colours[1])))(5) # Sequential palette
#pal <- colorRampPalette((c("#228096", "#BFBFBF","#EAD054")))(5) # Diverging palette
#pal <- viridis::viridis(5) # Alternative sequential palette

# Create chart 
choropleth_chart <- map_df %>% 
  # Set up a column with categorical labels for each bin
  mutate(items_per_1000_bin = cut(items_per_1000,
                                  breaks = c(0,5,10,15,20,Inf),
                                  labels = c("<5","5-10", "10-15", "15-20", "20+"))) %>% 
  ggplot() +
  # Plot the shapes. Add white borders for accesibility
  geom_sf(aes(fill = items_per_1000_bin), color = "#FFFFFF", lwd = 0.05) +
  # Apply our palette and adjust the legend
  scale_fill_manual(values = pal, 
                    na.value = "#808080",
                    guide = guide_legend(
                      title = "Items per 1000\npopulation",
                      title.hjust = 0.5,
                      label.position = "left",
                      reverse = TRUE)) +
  # Add another layer with the England shape to add an outline
  geom_sf(data = national_shape_df, fill = NA, color = "black", lwd = 0.1) +
  # Add the NICE theme
  nice_gg_theme() +
  # Alter some theme elements - remove axes, move the legend and add title
  theme(legend.position = c(0.15, 0.5),
        axis.text = element_blank(),
        axis.ticks.x = ggplot2::element_blank(),
        legend.title = ggplot2::element_text(family = "Inter",
                                             size = 12,
                                             color = "#000000"),
        legend.text.align = 1) +
  # Add labels
  labs(title = "Apixaban prescribing in England, 2021",
       subtitle = "Plotted by CCG")

choropleth_chart


Interactive charts

Choropleth map (Leaflet)

# Using same data as above

# Create chart
pal <- colorBin("plasma",
                domain = map_df$items_per_1000,
                bins = 5,
                na.color = "#808080")

leaflet_map <- map_df %>% 
    # Set up width and degree of zoom per click
    leaflet(width = 800, 
            options = leafletOptions(zoomDelta = 0.25,
                                     zoomSnap = 0.25)) %>% 
    # Set view to centre on England
    setView(lat = 53,
            lng = -1.5,
            zoom = 6) %>%
    # Add basic base tile
    addProviderTiles(provider = "CartoDB.Positron") %>%
    # Add shapes
    addPolygons(fillColor = ~pal(items_per_1000),
                fillOpacity = 0.3,
                color = "#393939",
                weight = 1.5,
                opacity = 1,
                layerId = ~ods_code,
                # Add functionality to highlight shape when hovered over
                highlight = highlightOptions(weight = 3,
                                             color = "#222222",
                                             fillOpacity = 0.7,
                                             bringToFront = TRUE),
                # Add labels upon hover
                label = ~lapply(paste0("<strong>Name: </strong>", sicbl22nm,
                                       "<br><strong>Items per 1000 population: </strong> ", 
                                       round(items_per_1000, 2)),
                                htmltools::HTML),
                labelOptions = labelOptions(textsize = "12px",
                                            style = list("font-family" = "Arial"))) %>%
    clearControls() %>%
    addLegend(position = "bottomleft",
              pal = pal,
              values = ~items_per_1000,
              title = "Items per 1000 population",
              opacity = 0.7)

leaflet_map